package db

import (
	"bytes"
	"fmt"
	"github.com/lishimeng/app-starter"
	"github.com/lishimeng/app-starter/persistence"
	"github.com/lishimeng/go-log"
	"time"
)

func CreateTable(name string, columns ...Column) (err error) {
	err = app.GetOrm().Transaction(func(tx persistence.TxContext) (e error) {
		exist, e := checkTableExist(tx, name)
		if e != nil {
			return
		}
		if !exist {
			e = createTable(tx, name, columns...)
			return
		}
		newColumns, e := checkColumns(tx, name, columns...)
		if e != nil {
			return
		}
		if len(newColumns) == 0 {
			log.Info("no new column skip")
			return
		}
		e = addColumns(tx, name, newColumns...)
		if e != nil {
			log.Info(e)
			return
		}
		return
	})
	return
}

func addColumns(tx persistence.TxContext, name string, columns ...Column) (err error) {
	var buf = bytes.NewBuffer(make([]byte, 0))
	buf.WriteString("-- --------------------------------------------------\n")
	buf.WriteString(fmt.Sprintf("--  Table Structure for `%s`\n", name))
	buf.WriteString("-- --------------------------------------------------\n")
	for _, column := range columns {
		ddl := DdlAddColumn(name, column)
		buf.WriteString(ddl)
		buf.WriteString("\n")
	}
	buf.WriteString("\n")
	_, err = tx.Context.Raw(buf.String()).Exec()
	if err != nil {
		log.Info("add column failed")
		log.Info(err)
		return
	}
	return
}

func checkTableExist(tx persistence.TxContext, name string) (exist bool, err error) {
	var tables []string
	_, err = tx.Context.Raw(ShowTablesQuery()).QueryRows(&tables)
	if err != nil {
		log.Info(err)
		return
	}
	for _, table := range tables {
		if table == name {
			exist = true
			break
		}
	}
	return
}

func createTable(tx persistence.TxContext, name string, columns ...Column) (err error) {
	ddl := DdlCreateTable(name, columns...)
	log.Info(ddl)
	_, err = tx.Context.Raw(ddl).Exec()
	if err != nil {
		log.Info("create table %s failed", name)
		log.Info(err)
		return
	}
	log.Info("create table %s success", name)
	return
}

func checkColumns(tx persistence.TxContext, table string, columns ...Column) (newColumns []Column, err error) {

	var curColumns []string
	ddl := ShowColumnsQuery(table)
	log.Info("query table columns: %s", ddl)
	_, err = tx.Context.Raw(ddl).QueryRows(&curColumns)
	if err != nil {
		log.Info(err)
		return
	}
	var columnMap = make(map[string]bool)
	for _, columnName := range curColumns {
		columnMap[columnName] = true
	}
	for _, column := range columns {
		_, ok := columnMap[column.ColumnName]
		if !ok {
			newColumns = append(newColumns, column)
		}
	}
	return
}

func SaveData(list []map[string]any, table string, columns ...Column) (err error) {
	var bufColumn []string
	var bufParam []string

	columns = append(columns, Column{
		ColumnName: "ctime",
		Field:      "ctime",
		Type:       "",
	})

	for _, column := range columns {
		bufColumn = append(bufColumn, column.ColumnName)
		bufParam = append(bufParam, "?")
	}
	ddl := DdlInsert(table, bufColumn, bufParam)

	log.Info("-- ------------------------------------------")
	log.Info("save %d data", len(list))
	log.Info(ddl)
	log.Info("-- ------------------------------------------")
	defer func() {
		log.Info("-- ------------------------------------------")
	}()
	err = app.GetOrm().Transaction(func(tx persistence.TxContext) (e error) {
		for _, data := range list {
			data["ctime"] = time.Now()
			var values []any
			values, e = genValues(data, columns...)
			if e != nil {
				log.Info(e)
				return
			}
			e = saveOneData(tx, ddl, values...)
			if e != nil {
				log.Info(e)
				return
			}
		}
		return
	})
	if err != nil {
		log.Info(err)
		return
	}

	return
}

func genValues(data map[string]any, columns ...Column) (values []any, err error) {
	for _, column := range columns {
		if v, ok := data[column.Field]; ok {
			values = append(values, v)
		} else {
			err = fmt.Errorf("field %s not exist", column.Field)
			break
		}
	}
	return
}

func saveOneData(tx persistence.TxContext, ddl string, data ...any) (err error) {
	log.Info(ddl)
	log.Info("with values: %v", data)
	_, err = tx.Context.Raw(ddl, data...).Exec()
	return
}
